package com.imcreator.icchat;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;

import com.esotericsoftware.yamlbeans.YamlReader;
import com.esotericsoftware.yamlbeans.YamlWriter;
import com.imcreator.icchat.Objects.Client;
import com.imcreator.icchat.ServerThreads.AcceptThread;
import com.imcreator.icchat.ServerThreads.ClientThread;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;

public class Methods {

    /*
     * ==================================
     * 方法名：changeColor(String text, String color)
     * 功能：改变文本颜色
     * =================================
     */
    public static String changeColor(String text, String color) {
        return color + text + Colors.RESET;
    }

    /*
     * ==================================
     * 方法名：connect(String ip, String port)
     * 功能：向指定的地址发起socket连接请求
     * =================================
     */
    public static Socket connect(String ip, String port) {
        try {
            printAbove(Methods.changeColor(
                "正在连接服务器(" + ip + ":" + port + ")...", 
                Colors.YELLOW_BOLD));
            Socket socket = new Socket(ip, Integer.valueOf(port));
            return socket;
        } catch (UnknownHostException e) {
            printAbove(Methods.changeColor(
                "不明主机: " + ip, 
                Colors.RED_BOLD));
            e.printStackTrace();
        } catch (IOException e) {
            printAbove(Methods.changeColor(
                "无法连接到: " + ip, 
                Colors.RED_BOLD));
            e.printStackTrace();
        }
        return null;
    }

    /*
     * =================================
     * 方法名：isSpaces(String str)
     * 功能：判断整个字符串中是否存在连续两个及以上
     * 的空格
     * =================================
     */
    public static boolean hasSpaces(String str) {
        int i = 0;
        for (char c : str.toCharArray()) {
            if (c == ' ') {
                i++;
            } else {
                i = 0;
            }
            if (i >= 2) {
                return true;
            }
        }
        return false;
    }

    /*
     * =================================
     * 方法名：start()
     * 功能：启动Socket服务端
     * =================================
     */
    public static void start() {
        try {
            ServerSocket serverSocket = new ServerSocket(IcChat.port);
            printAbove(Methods.changeColor(
                "服务端正在" + IcChat.port + "端口运行", 
                Colors.YELLOW_BOLD));
            IcChat.acceptThread = new AcceptThread(serverSocket);
            IcChat.acceptThread.start();
            printAbove(Methods.changeColor(
                "服务端监听线程已启动", 
                Colors.GREEN_BOLD));
            IcChat.serverRunning = true;
        } catch (IOException e) {
            printAbove(Methods.changeColor(
                "无法在" + IcChat.port + "端口启动服务端, 请检查端口是否被占用", 
                Colors.RED_BOLD));
        }
    }

    /*
     * =================================
     * 方法名：checkFile()
     * 功能：启动程序时检查配置文件
     * =================================
     */
    public static void checkFile() {
        printAbove("当前操作系统: " + System.getProperty("os.name"));
        String dir = System.getProperty("user.dir"); // 获取程序启动目录
        printAbove("正在检查程序启动目录(" + dir + ")...");

        String dir_icchat;
        String dir_config;

        if (isWindows()) {
            dir_icchat = dir + "\\icchat";
            dir_config = dir_icchat + "\\config.yml";
        } else {
            dir_icchat = dir + "/icchat";
            dir_config = dir_icchat + "/config.yml";
        }
        File file = new File(dir_icchat);
        File config = new File(dir_config);
        Map<String, Object> map = new HashMap<>();
        Boolean replace = false;
        if (!file.exists()) { // 创建目录
            if (file.mkdirs()) {
                printAbove(changeColor(
                    "程序目录创建成功(" + dir_icchat + ")", 
                    Colors.GREEN_BOLD));
            } else {
                printAbove(Methods.changeColor(
                    "程序目录创建失败，程序终止", 
                    Colors.RED_BOLD));
                IcChat.isExit = true;
                return;
            }
        }

        if (config.exists()) {
            try {
                printAbove(changeColor(
                    "正在读取配置文件(" + dir_config + ")", 
                    Colors.YELLOW_BOLD));
                YamlReader reader = new YamlReader(new FileReader(dir_config));
                reader.getConfig().setClassTag("IC-Config", LinkedHashMap.class);
                Object object = reader.read();
                map = BeanUtil.beanToMap(object);
                reader.close();
            } catch (IOException e) {
                printAbove(Methods.changeColor(
                    "读取配置文件时出错，程序终止", 
                    Colors.RED_BOLD));
                IcChat.isExit = true;
                e.printStackTrace();
            }

            if (!String.valueOf(map.get("version")).equals("1")) { // 判断是否要更新配置文件(请保证此处版本与配置文件中版本相同)
                printAbove(Methods.changeColor(
                    "配置文件版本过旧，开始更新...", 
                    Colors.YELLOW_BOLD));
                replace = true;
            } else {
                return;
            }
        } else { // 创建config
            try {
                if (!config.createNewFile()) {
                    printAbove(Methods.changeColor(
                        "配置文件创建失败，程序终止", 
                        Colors.RED_BOLD));
                    IcChat.isExit = true;
                    return;
                }
            } catch (IOException e) {
                printAbove(Methods.changeColor(
                    "配置文件创建失败，程序终止", 
                    Colors.RED_BOLD));
                IcChat.isExit = true;
                e.printStackTrace();
            }
        }

        try { // 复制config
            InputStream inputStream = Methods.class.getResourceAsStream("/config.yml");
            Path externalPath = Paths.get(dir_config);
            Files.copy(inputStream, externalPath, StandardCopyOption.REPLACE_EXISTING);
            printAbove(Methods.changeColor(
                "配置文件创建成功(" + dir_config + ")", 
                Colors.GREEN_BOLD));
        } catch (IOException e) {
            printAbove(Methods.changeColor(
                "配置文件创建失败，程序终止", 
                Colors.RED_BOLD));
            IcChat.isExit = true;
            e.printStackTrace();
        }

        if (replace) { // 替换模块
            try {
                YamlReader reader = new YamlReader(new FileReader(dir_config));
                reader.getConfig().setClassTag("IC-Config", LinkedHashMap.class);
                Object object = reader.read();
                Map<String, Object> newMap = BeanUtil.beanToMap(object);
                reader.close();

                for (String key : map.keySet()) { // 替换内容
                    if (key.equalsIgnoreCase("version")) {
                        continue;
                    }
                    if (newMap.containsKey(key)) {
                        newMap.replace(key, map.get(key));
                    }
                }

                YamlWriter writer = new YamlWriter(new FileWriter(dir_config)); // 写入内容
                writer.getConfig().setClassTag("IC-Config", LinkedHashMap.class);
                writer.write(newMap);
                writer.close();
                printAbove(Methods.changeColor(
                    "配置文件更新完成", 
                    Colors.GREEN_BOLD));
            } catch (IOException e) {
                printAbove(Methods.changeColor(
                    "配置文件创建失败，程序终止", 
                    Colors.RED_BOLD));
                IcChat.isExit = true;
                e.printStackTrace();
            }
        }
    }

    /*
     * =================================
     * 方法名：reloadConfig()
     * 功能：读取配置文件到内存中
     * 备注：要在检查配置文件后再调用
     * =================================
     */
    public static void reloadConfig() {
        String dir_config;
        if (isWindows()) {
            dir_config = System.getProperty("user.dir") + "\\icchat\\config.yml";
        } else {
            dir_config = System.getProperty("user.dir") + "/icchat/config.yml";
        }
        try {
            YamlReader reader = new YamlReader(new FileReader(dir_config));
            reader.getConfig().setClassTag("IC-Config", LinkedHashMap.class);
            Object object = reader.read();
            IcChat.configMap = BeanUtil.beanToMap(object);
            reader.close();
        } catch (IOException e) {
            printAbove(Methods.changeColor(
                "读取配置文件时出错，程序终止", 
                Colors.RED_BOLD));
            IcChat.isExit = true;
            e.printStackTrace();
        }
    }

    /*
     * =================================
     * 方法名：saveConfig()
     * 功能：读取配置文件到内存中
     * 备注：要在检查配置文件后再调用
     * =================================
     */
    public static void saveConfig() {
        try {
            String dir_config;
            if (isWindows()) {
                dir_config = System.getProperty("user.dir") + "\\icchat\\config.yml";
            } else {
                dir_config = System.getProperty("user.dir") + "/icchat/config.yml";
            }
            YamlWriter writer = new YamlWriter(new FileWriter(dir_config)); // 保存内容
            writer.getConfig().setClassTag("IC-Config", LinkedHashMap.class);
            writer.write(IcChat.configMap);
            writer.close();
        } catch (Exception e) {
            printAbove(Methods.changeColor(
                "保存配置文件时出错，程序终止", 
                Colors.RED_BOLD));
            IcChat.isExit = true;
            e.printStackTrace();
        }
    }

    /*
     * =================================
     * 方法名：checkName()
     * 功能：启动程序时检查配置文件
     * =================================
     */
    public static Boolean checkName(String name) {
        if (name.isBlank() || name.length() > 16)
            return false;
        for (char c : name.toCharArray()) {
            if (!Character.isLetterOrDigit(c)) {
                if (c != '_')
                    return false;
            }
        }
        return true;
    }

    /*
     * =================================
     * 方法名：checkPort()
     * 功能：检查端口号是否合法
     * =================================
     */
    public static Boolean checkPort(String port) {
        Integer input = 0;
        try {
            input = Integer.parseInt(port);
        } catch (NumberFormatException e) {
            printAbove("输入不合法, 请重新输入");
            return false;
        }
        if (input <= 0 || input > 65535) {
            printAbove("端口超出范围, 请重新输入");
            return false;
        }
        return true;
    }

    /*
     * =================================
     * 方法名：showTitle()
     * 功能：启动程序时随机展示超帅的启动界面
     * =================================
     */
    public static void showTitle() {
        String title;
        // int randNumber = rand.nextInt(MAX - MIN + 1) + MIN;
        switch (new Random().nextInt(4)) {
            case 0:
                title = "    _              __          __ \n" + //
                        "   (_)____   _____/ /_  ____ _/ /_\n" + //
                        "  / / ___/  / ___/ __ \\/ __ `/ __/\n" + //
                        " / / /__   / /__/ / / / /_/ / /_  \n" + //
                        "/_/\\___/   \\___/_/ /_/\\__,_/\\__/  \n" + //
                        "                                  ";
                break;
            case 1:
                title = " __     ______        ______     __  __     ______     ______  \n" + //
                        "/\\ \\   /\\  ___\\      /\\  ___\\   /\\ \\_\\ \\   /\\  __ \\   /\\__  _\\ \n" + //
                        "\\ \\ \\  \\ \\ \\____     \\ \\ \\____  \\ \\  __ \\  \\ \\  __ \\  \\/_/\\ \\/ \n" + //
                        " \\ \\_\\  \\ \\_____\\     \\ \\_____\\  \\ \\_\\ \\_\\  \\ \\_\\ \\_\\    \\ \\_\\ \n" + //
                        "  \\/_/   \\/_____/      \\/_____/   \\/_/\\/_/   \\/_/\\/_/     \\/_/ \n" + //
                        "";
                break;
            case 2:
                title = " _             _           _   \n" + //
                        "(_)           | |         | |  \n" + //
                        " _  ___    ___| |__   __ _| |_ \n" + //
                        "| |/ __|  / __| '_ \\ / _` | __|\n" + //
                        "| | (__  | (__| | | | (_| | |_ \n" + //
                        "|_|\\___|  \\___|_| |_|\\__,_|\\__|\n" + //
                        "                               ";
                break;
            case 3:
                title = "    dMP .aMMMb \n" + //
                        "   amr dMP\"VMP \n" + //
                        "  dMP dMP      \n" + //
                        " dMP dMP.aMP   \n" + //
                        "dMP  VMMMP\"    \n" + //
                        "               ";
                break;
            default:
                title = ".__                        .__            __   \n" + //
                        "|__| ____             ____ |  |__ _____ _/  |_ \n" + //
                        "|  |/ ___\\   ______ _/ ___\\|  |  \\\\__  \\\\   __\\\n" + //
                        "|  \\  \\___  /_____/ \\  \\___|   Y  \\/ __ \\|  |  \n" + //
                        "|__|\\___  >          \\___  >___|  (____  /__|  \n" + //
                        "        \\/               \\/     \\/     \\/      ";
                break;
        }
        printAbove(ranmdowColor(title));
    }

    /*
     * =================================
     * 方法名：ranmdowColor(String text)
     * 功能：将文本附上随机的颜色
     * =================================
     */
    public static String ranmdowColor(String text) {
        return changeColor(text, Colors.getBoldColors().get(new Random().nextInt(6)));
    }

    /*
     * =================================
     * 方法名：isWindows()
     * 功能：判断是否为windows操作系统
     * =================================
     */
    public static boolean isWindows() {
        return System.getProperty("os.name").toUpperCase().indexOf("WINDOWS") >= 0 ? true : false;
    }

    /*
     * =================================
     * 方法名：sendMessage()
     * 功能：打包内容并用Json发送
     * 返回值：Boolean表示是否发送成功
     * =================================
     */
    public static Boolean sendMessage(Socket socket, String type, String content) {
        Map<String, Object> messageMap = new HashMap<>();
        messageMap.put("type", type);
        messageMap.put("content", content);
        return sendMessage(socket, JSONUtil.toJsonStr(messageMap));
    }
    public static Boolean sendMessage(Socket socket, String jsonMessage) {
        try {
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            bufferedWriter.write(jsonMessage);
            bufferedWriter.newLine();
            bufferedWriter.flush();
            return true;
        } catch (IOException e) {
            printAbove(Methods.changeColor(
                "尝试与远端通信时出现错误", 
                Colors.RED_BOLD));
            e.printStackTrace();
        }
        return false;
    }

    /*
     * =================================
     * 方法名：printAbove()
     * 功能：使用JLine发送控制台信息
     * =================================
     */
    public static void printAbove(String string) {
        if (IcChat.lineReader == null) {
            IcChat.terminal.writer().println(string);
        } else {
            IcChat.lineReader.printAbove(string);
        }
    }

    /*
     * =================================
     * 方法名：kickClient(String name, String reason)
     * 功能：将某个客户端踢出服务器
     * =================================
     */
    public static void kickClient(String clientName, String reason) {
        if (!IcChat.clientsMap.containsKey(clientName)) {
            return;
        }
        if (reason == null) {
            reason = "Unknow";
        }

        Client client = IcChat.clientsMap.get(clientName);

        Map<String, String> messageMap = new HashMap<>();
        messageMap.put("type", "info");
        messageMap.put("content", "kick");
        messageMap.put("reason", reason);
        sendMessage(client.getSocket(), JSONUtil.toJsonStr(messageMap));

        IcChat.clientsMap.remove(clientName);
        stopClientThread(client);
    }

    /*
     * =================================
     * 方法名：disconnect(String name)
     * 功能：与某个客户端断开连接
     * =================================
     */
    public static void disconnect(String clientName) {
        if (!IcChat.clientsMap.containsKey(clientName)) {
            return;
        }
        Client client = IcChat.clientsMap.get(clientName);
        sendMessage(client.getSocket(), "info", "disconnect");
        stopClientThread(client);
    }
    public static void disconnect(Client client) {
        sendMessage(client.getSocket(), "info", "disconnect");
        stopClientThread(client);
    }

    /*
     * =================================
     * 方法名：stopClientThread()
     * 功能：停止客户端线程
     * =================================
     */
    public static void stopClientThread(Client client) {
        ClientThread clientThread = client.getClientThread();
        clientThread.connecting = false;
        try {
            clientThread.bufferedReader.close();
        } catch (IOException e) {
            Methods.printAbove(Methods.changeColor(
                "关闭" + client.getName() + "客户端(" + client.getSocket().getInetAddress() + ")的线程时出错", 
                Colors.RED_BOLD));
            e.printStackTrace();
        }
    }
    public static void stopClientThread(ClientThread clientThread) {
        clientThread.connecting = false;
        try {
            clientThread.bufferedReader.close();
        } catch (IOException e) {
            Methods.printAbove(Methods.changeColor(
                "关闭客户端线程(ID: " + clientThread.getId() + ")时出错", 
                Colors.RED_BOLD));
            e.printStackTrace();
        }
    }

    /*
     * =================================
     * 方法名：broadcastMessage(String message)
     * 功能：向所有客户端广播聊天信息
     * =================================
     */
    public static void broadcastMessage(String message, Collection<String> nameBlackList) {
        broadcastJson("message", message, nameBlackList);
    }

    /*
     * =================================
     * 方法名：broadcastJson()
     * 功能：向所有客户端广播Json信息
     * =================================
     */
    public static void broadcastJson(String type, String content, Collection<String> nameBlackList) {
        Map<String, String> messageMap = new HashMap<>();
        messageMap.put("type", type);
        messageMap.put("content", content);
        broadcastJson(JSONUtil.toJsonStr(messageMap), nameBlackList);
    }
    public static void broadcastJson(String jsonMessage, Collection<String> nameBlackList) {
        if (nameBlackList == null) {
            nameBlackList = Collections.emptyList();
        }
        for (Client client : IcChat.clientsMap.values()) {
            if (nameBlackList.contains(client.getName())) {
                continue;
            }
            sendMessage(client.getSocket(), jsonMessage);
        }
    }

    /*
     * =================================
     * 方法名：stop()
     * 功能：关闭服务端
     * =================================
     */
    public static void stop() {
        broadcastMessage(
            Methods.changeColor("正在关闭服务器，连接将被断开", Colors.CYAN_BOLD), 
            null);

        IcChat.acceptThread.running = false;
        IcChat.acceptThread.interrupt();
        Methods.printAbove(Methods.changeColor(
            "服务端监听线程已停止", 
            Colors.YELLOW_BOLD));
        
        for (ClientThread clientThread : IcChat.verifingList) {
            sendMessage(clientThread.socket, "unaccepted", "shutdown");
            stopClientThread(clientThread);
        }
        IcChat.verifingList = new ArrayList<>();
        for (String clientName : IcChat.clientsMap.keySet()) {
            disconnect(clientName);
        }
        IcChat.clientsMap = new HashMap<>();
        Methods.printAbove(Methods.changeColor(
            "已断开所有客户端的连接", 
            Colors.YELLOW_BOLD));
        
        IcChat.serverRunning = false;
        try {
            IcChat.acceptThread.serverSocket.close();
        } catch (IOException e) {
            Methods.printAbove(Methods.changeColor(
                "关闭服务端时出错", 
                Colors.RED_BOLD));
            e.printStackTrace();
        }
        IcChat.acceptThread = null;
        Methods.printAbove(Methods.changeColor(
            "服务端已停止", 
            Colors.GREEN_BOLD));
    }

    /*
     * =================================
     * 方法名：onlineClientsToStringList()
     * 功能：获取在线客户端列表字符串
     * =================================
     */
    public static String onlineClientsToStringList(Map<String, Client> map) {
        String result = Methods.changeColor("当前在线客户端：", Colors.CYAN_BOLD);
        for (String clientName : map.keySet()) {
            result = result + "\n" + clientName + " - " + map.get(clientName).getSocket().getInetAddress();
        }
        return result;
    }
    public static String onlineClientsToStringList(List<String> list) {
        String result = Methods.changeColor("当前在线客户端：", Colors.CYAN_BOLD);
        for (String clientName : list) {
            result = result + "\n" + clientName;
        }
        return result;
    }
}
